home *** CD-ROM | disk | FTP | other *** search
/ Magnum One / Magnum One (Mid-American Digital) (Disc Manufacturing).iso / d18 / kruse_11.arc / INDEXWRI.PAS < prev    next >
Pascal/Delphi Source File  |  1990-11-30  |  36KB  |  956 lines

  1. {outline of declaration of subprograms:
  2.  
  3.  1.     program IndexText(InText, InIndex, NewIndex, OutIndex, HashFile,
  4.                           NewHashFile, input, output);     (main program)
  5.  2.         function Lt(u, v: word):  Boolean;
  6.  3.         procedure ReadWord(var f: text;  var w: word);
  7.  4.         procedure WriteWord(var f: text; w: word);
  8.  4a.        built in CPU time function   clock;
  9.  
  10.  5.         procedure SplitWords;                       (phase 1)
  11.  5a.            function FindFile(ch: char): filecode;
  12.  6.             function HashAddress(w: word):  hashentry;
  13.  7.             procedure Initialize;
  14.  8.             procedure GetWord;
  15.  8a.                procedure TellUserPage;
  16.  9.                 procedure GetChar(var ch: char);
  17. 10.                 procedure AddChar(ch: char);
  18. 11.             procedure Conclude;
  19.  
  20. 12.         procedure ClassifyWords;                    (phase 2)
  21. 13.             procedure BuildTree(var root: pointer; ch: char);
  22. 15.                 function Power2(c: integer): level;
  23.                   (the next three procedures are written in line.)
  24. 14.                 procedure Insert(p: pointer);
  25. 16.                 procedure FindRoot;
  26. 17.                 procedure ConnectSubtrees;
  27. 18.                 procedure GetNode(var p: pointer; ch: char);
  28. 19.             procedure Process(r: reference);
  29. 20.                 procedure UpdateNode(p: pointer; r: reference);
  30. 21.                 procedure NewWord(var p: pointer; r: reference);
  31. 22.                 procedure InsertTree(r, p: pointer);
  32. 23.             procedure OutputTree(p: pointer);
  33. 24.                 procedure PutNode(p: pointer);
  34. }
  35.  
  36.  
  37. program IndexText(InText, InIndex, NewIndex, HashFile, NewHashFile,
  38.                   input, output);
  39.  
  40. {Produces word counts and list of references for the document file 
  41.  InText. Uses the master word list in file InIndex, if provided. Output word
  42.  list for new text goes to file NewIndex. HashFile contains the common words
  43.  to be ignored. If not specified, it is created on output, containing the
  44.  words so flagged by the user.}
  45. {This implementation uses only phases 1 and 2. A smaller array of text files
  46.  is also used, as specified in the exercise section.}
  47.  
  48. const
  49.   maxwd         =   20;             {More letters in word will be ignored.}
  50.   minwd         =    1;                    {Shorter words will be ignored.}
  51.   hashsize      = 2003;                                 {should be a prime}
  52.   linesperpage  =   66;                {assumes standard spacing and paper}
  53.   maxheight     =   20;               {for building binary tree in phase 2}
  54.   A             =  'A';
  55.   Z             =  'Z';
  56.   hyphen        =  '-';
  57.   blank         =  ' ';
  58.   apostrophe    = '''';               {requires two `'s  to represent one}
  59.   underscore    =  '_';
  60.   ordbackspace  =    8;            {ASCII control character for backspace}
  61.   ordformfeed   =   12;             {ASCII control character for new page}
  62.   changecase    =   32;    {ASCII difference between upper and lower case}
  63.   nfiles        =    8;  {number of temporary files for unprocessed words}
  64.   MaxRowLength  =   130;                 {maximum length of output records}
  65.  
  66. type
  67.   word          =  packed array[1..maxwd] of char;
  68.   reference     =  record
  69.                       wd:   word;
  70.                       pg:   integer;               {count or page number}
  71.                    end;
  72.   fileref       =  file of reference;              {used for local files}
  73.   letter        =  A..Z;
  74.   hashentry     =  1..hashsize;
  75.   filecode      =  1..nfiles;
  76.  
  77. var
  78.   InText,                                     {document being processed}
  79.   InIndex,                                            {master word list}
  80.   NewIndex,                              {word list of current document}
  81.   HashFile,
  82.   NewHashFile:      text;
  83.   RefFile:      array[filecode] of fileref; {local files used for auxilary
  84.                                storage of words from phase 1 to phase 2:
  85.                 Normally, a separate file exist for each initial letter,
  86.         this version uses nfiles files due operating system constraints.}
  87.   blankword:    word;                           {will contain all blanks}
  88.  
  89. {The next two variables were originally declared in procedure SplitWords,
  90.  they have been moved to this level in order to access them globally.}
  91.   outcount:     array[filecode] of integer;    {counters for word  files}
  92.   wordcount:    integer;                 {count of all words in the text}
  93.  
  94.   intextname,
  95.   inlistname,
  96.   newlistname,
  97.   newhashname:  word;                    {used to get filename from user}
  98.   lastletter:   array[filecode] of letter;     {last letter in each file}
  99.   PresentTime,
  100.   StartTime:    integer;                         {used to track CPU time}
  101.   RowLength:    integer;   {ensures records will not exceed MaxRowLength}
  102.  
  103.  
  104. function Lt( u, v: word): Boolean;
  105. {Determains if word u precedes word v lexicographically.}
  106. begin
  107.   Lt := (u < v)
  108. end;
  109.  
  110. procedure ReadWord( var F: text;  var w: word);
  111. {Reads word w from text file F.  Assumes not at end of file.}
  112. {Uses packed array, replace using a loop if your system does not 
  113.  support packed arrays. }
  114. begin                           {procedure ReadWord}
  115.   read(F, w)
  116. end;                            {procedure ReadWord}
  117.  
  118. procedure WriteWord( var F: text; w: word);
  119. {Writes word w to text file F}
  120. {Uses packed array, replace using a loop if your system does not 
  121.  support packed arrays. }
  122. begin                           {procedure WriteWord}
  123.   write(F, w)
  124. end;                            {procedure WriteWord}
  125.  
  126. procedure SetTimer;     {Call once at beginning of program execution.}
  127. {Finds the CPU time when called, and keeps in variables for reference.}
  128. {System dependent procedure.}
  129. begin
  130.   PresentTime := clock;
  131.   StartTime := PresentTime;
  132. end;
  133.  
  134. function TotalTime:  real;
  135. {Returns the total CPU time, in seconds, since call to SetTimer.}
  136. {System dependent procedure.}
  137. begin
  138.   TotalTime := (clock - StartTime) / 1000.0;
  139. end;
  140.  
  141. function ElapsedTime:  real;
  142. {Returns elapsed CPU time since last call to function ElapsedTime,
  143.  or call to SetTimer, whichever is more recent.}
  144. {System dependent procedure.}
  145. var r: integer;
  146. begin
  147.   r := clock;
  148.   ElapsedTime := (r - PresentTime) / 1000.0;
  149.   PresentTime := r;
  150. end;
  151.  
  152.  
  153. procedure SplitWords;
  154. {sets up hash table, reads text, and divides into nfiles word lists}
  155.  
  156. var
  157.   hash:       array[hashentry] of reference;              {hash table}
  158.   pagecount:  integer;                 {keeps the current page number}
  159.   addpage:    integer;       {amount to increase pagecount after word}
  160.   linecount:  integer;                     {lines on the current page}
  161.   w:          word;                   {word currently being processed}
  162.   x:          hashentry;             {location of w, if in hash table}
  163.   endinput:   Boolean;   {true if and only if input has all been read}
  164.   code:       filecode;                {into which file does word go?}
  165.  
  166. {The following variables are kept for use in procedure GetWord, and for
  167.  efficiency are set up only once in procedure Initialize:}
  168.   backspace,
  169.   formfeed:   char;
  170.   alphabet,                           {letters only - to start a word}
  171.   contchar:   set of char;     {other characters ok in middle of word}
  172.  
  173.  
  174.   function  FindFile( ch:  letter):  filecode;
  175.   {Uses binary decision tree to select one of nfiles = 8 files depending
  176.    on the letter ch.  These letters must be the same as those in the
  177.    global array  lastletter  .}
  178.  
  179.   begin                           {function FindFile}
  180.     if            ch < 'M' then
  181.       if          ch < 'E' then
  182.         if        ch < 'C' then  FindFile := 1
  183.                            else  FindFile := 2
  184.       else if     ch < 'H' then  FindFile := 3
  185.                            else  FindFile := 4
  186.     else if       ch < 'S' then
  187.       if          ch < 'P' then  FindFile := 5
  188.                            else  FindFile := 6
  189.       else if     ch < 'T' then  FindFile := 7
  190.                            else  FindFile := 8
  191.   end;                            {function FindFile}
  192.  
  193.  
  194.   function HashAddress(w: word): hashentry;
  195.   {calculates the location in hash table of word w, or, if not there,
  196.    returns pointing to the blank word where w should go}
  197.  
  198.   var
  199.     x,                            {calculated location}
  200.     inc:     integer;             {increment for open addressing}
  201.   begin                           {function HashAddress}
  202.     x := abs(ord(w[1])*ord(w[2])+ord(w[4])+ord(w[6])) mod hashsize + 1;
  203. {Hash function assumes long word length. For short word machines
  204.  we must ensure that the result is non-negative, and worry about overflow.}
  205.  
  206.     if (hash[x].wd <> w) and (hash[x].wd <> blankword) then
  207.       begin
  208.         inc   := (abs(ord(w[3])-95) mod 29);
  209.                   {A key dependent increment is used to avoid clustering.}
  210.         repeat
  211.           inc := inc + 1;
  212.           if inc > hashsize then
  213.             writeln(w,' causes hash table to become full, infinite loop.');
  214.           x := x + inc;
  215.           if x > hashsize then x := x - hashsize;
  216.         until (w =  hash[x].wd)  or  (blankword = hash[x].wd)
  217.       end;
  218.     HashAddress := x
  219.   end;                            {function HashAddress}
  220.  
  221.  
  222.   procedure Initialize;
  223.   {sets up constant-valued sets for use in GetWord. Opens the text file
  224.    and initializes various counters. Opens file holding hash table (if any),
  225.    and reads or otherwise initializes table}
  226.   var
  227.     i:         integer;          {general purpose loop control}
  228.  
  229.   begin                           {procedure Initialize}
  230.     backspace:= chr(ordbackspace);
  231.     formfeed := chr(ordformfeed); {initialize ASCII control characters}
  232.     alphabet := ['A'..'Z', 'a'..'z'];      {letters only, to start a word}
  233.     contchar := [hyphen, apostrophe, backspace, underscore];
  234.                                 {characters which will not terminate word}
  235.     for i := 1 to maxwd do
  236.       blankword[i] := blank;
  237.  
  238.     write('Name of input text file?');
  239.     ReadWord(input, intextname); readln;
  240.     open(InText, intextname, readonly);
  241.     reset(InText);
  242.     endinput := eof(InText);
  243.  
  244.     repeat
  245.       write( 'What is the page number on which the text begins?');
  246.       readln(pagecount);
  247.       if pagecount < 0 then
  248.         writeln('Must be a non-negative integer.')
  249.     until pagecount >= 0;
  250.     linecount := 0;
  251.     addpage   := 0;
  252.     wordcount := 0;
  253.  
  254.     for i := 1 to nfiles do
  255.     begin
  256.       rewrite( RefFile[i] );
  257.       outcount[i] := 0
  258.     end;
  259.     lastletter[1] := 'B';
  260.     lastletter[2] := 'D';
  261.     lastletter[3] := 'G';
  262.     lastletter[4] := 'L';
  263.     lastletter[5] := 'O';
  264.     lastletter[6] := 'R';
  265.     lastletter[7] := 'S';
  266.     lastletter[8] := 'Z';
  267.  
  268.     reset(HashFile);   {assumes HASHFILE.DAT is in current directory}
  269.  
  270.     for i := 1 to hashsize do
  271.     with hash[i] do 
  272.       begin
  273.         read(HashFile, pg);
  274.         get(HashFile);         {skip the blank between number and word}
  275.         ReadWord(HashFile, wd);
  276.         readln(HashFile);
  277.         pg := 0;                     {initialize all the counts to 0}
  278.       end;
  279.     writeln('The hash table has been read.')
  280.   end;                                        {procedure Initialize}
  281.  
  282.  
  283.   procedure GetWord( var  w: word);
  284.   {Gets words from input file InText, and returns only words
  285.    at least minwd characters long.  Parameter endinput becomes
  286.    true if and only if the end of InText is reached with no word to return.
  287.    the procedure also updates global variables wordcount and linecount,
  288.    updates the global variable pagecount after each linesperpage cr's,
  289.    or after each formfeed, whichever comes first, and
  290.    uses the sets alphabet and contchar and various character constants.}
  291.  
  292.   label 1;           {used by GetChar to exit procedure upon eof(InText)}
  293.  
  294.   var  c:      0..maxwd;                    {count of characters in word}
  295.        ch:     char;                      {character currently processed}
  296.        endln:  Boolean;                           {at the end of a line?}
  297.  
  298.  
  299.   procedure TellUserPage;         {keep the user informed of progress}
  300.   var   i: integer;
  301.   begin
  302.     i := pagecount + addpage;
  303.     writeln('At page', i:4, ' word count is', wordcount:7)
  304.   end;
  305.  
  306.  
  307.   procedure GetChar(var ch: char);
  308.   {gets a character from input text into ch; checks for eof; updates
  309.    page count and line count}
  310.  
  311.   begin                                                {procedure GetChar}
  312.     if eof(InText) then
  313.       if c >= minwd then
  314.         ch := '.'              {special character to end the current word}
  315.       else begin                         {no word to return; set endinput}
  316.         endinput := true;
  317.         goto 1                                        {exit from GetWord.}
  318.       end
  319.     else begin                   {not end of file: process next character}
  320.       while InText^ in [underscore, backspace] do
  321.         get( InText);
  322.       ch := InText^;
  323.       endln := eoln(InText);
  324.       get(InText);
  325.       if endln then
  326.       begin
  327.         linecount := linecount + 1;
  328.         if linecount >= linesperpage then
  329.           begin
  330.             addpage := addpage + 1;
  331.             linecount := 0;
  332.             TellUserPage
  333.           end
  334.       end;
  335.       if ch = formfeed then
  336.         begin
  337.           addpage := addpage + 1;
  338.           linecount := 0;
  339.           TellUserPage;
  340.           endln := true;            {Treat formfeed like end of line.}
  341.           ch := blank
  342.         end
  343.     end
  344.   end;                                            {procedure GetChar}
  345.  
  346.  
  347.   procedure AddChar(ch: char);
  348.   {adds given character to word, if possible}
  349.   begin                           {procedure AddChar}
  350.     if c < maxwd then
  351.     begin
  352.       c := c + 1;
  353.       w[c] := ch
  354.     end
  355.   end;                            {procedure AddChar}
  356.  
  357.  
  358.   begin                           {procedure GetWord}
  359.     repeat                {until current word is at least minwd chars long}
  360.       c := 0;
  361.       repeat
  362.         GetChar(ch)               {Find a letter which will start the word.}
  363.       until ch in alphabet;
  364.       pagecount := pagecount + addpage;
  365.       addpage := 0;
  366.       if ch in ['a'..'z'] then       {translate first letter to upper case.}
  367.         ch := chr(ord(ch) - changecase); {assumes ASCII ordering of letters}
  368.       AddChar(ch);                          {put first letter into the word}
  369.       GetChar(ch);
  370.       while (ch in alphabet) or (ch in contchar) do
  371.         if ch in alphabet then                {add letters directly to word}
  372.         begin                                            {processing letter}
  373.           AddChar(ch);
  374.           GetChar(ch)
  375.         end                                              {processing letter}
  376.         else if ch = hyphen then
  377.         begin                                            {processing hyphen}
  378.           GetChar(ch);                       {Find what comes after hyphen.}
  379.           if endln then
  380.             while ch = ' ' do
  381.               GetChar(ch)       {Delete both the hyphen and the end of line}
  382.           else if ch = hyphen then      {Two hyphens form a dash; ends word}
  383.             ch := blank                 {Use a blank to terminate the word.}
  384.           else if ch in alphabet then
  385.             AddChar(hyphen)                  {Include other hyphens in word}
  386.           else      {nothing}
  387.         end                                              {processing hyphen}
  388.         else if ch = apostrophe then
  389.         begin                                        {processing apostrophe}
  390.           GetChar(ch);
  391.           if ch = 's' then              {Delete  `'s'   at end of word only}
  392.           begin
  393.             GetChar(ch);
  394.             if ch in contchar then
  395.             begin
  396.               AddChar(apostrophe);
  397.               AddChar('s')
  398.             end
  399.           end
  400.           else if ch in alphabet then
  401.              AddChar(apostrophe)                      {Allow contractions.}
  402.         end                                         {processing apostrophe}
  403.         else         {Remaining possibilities are backspace and underscore.}
  404.           GetChar(ch);                           {Delete these characters.}
  405.       {While loop on continuing characters ends here.}
  406.       wordcount := wordcount + 1
  407.     until c >= minwd;                              {Skip over short words.}
  408.  
  409.     while c < maxwd do                                  {Fill with blanks.}
  410.     begin
  411.       c := c + 1;
  412.       w[c] := blank
  413.     end;
  414.   1:      {When end of file occurs, program will exit to here from GetChar}
  415.   end;                                                  {procedure GetWord}
  416.  
  417.  
  418. procedure Conclude;
  419. {Writes out counts of various word lists. For some systems, it is 
  420.  necessary to close files, which should be done here.}
  421.  
  422. var
  423.   i,j:        integer;                                {loop index}
  424.   response:   char;                    {user's answer to question}
  425.  
  426. begin                           {procedure Conclude}
  427.   writeln('The total number of words read in is ', wordcount:7);
  428.   writeln;
  429.   writeln('The number of words to process further in the next stage,');
  430.   writeln('in each temporary file, is below.');
  431.   writeln('     a-b     c-d     e-g     h-l     m-o     p-r      s      t-z');
  432.   for i := 1 to nfiles do
  433.     write(outcount[i]:8);
  434.   writeln;
  435.   writeln;
  436.  
  437.   repeat
  438.     write('Do you wish the counts from hash table to be kept in a file (y,n)?');
  439.     readln(response);
  440.     if response > 'Z' then response := chr(ord(response)-changecase)
  441.   until response in ['N', 'Y'];
  442.   if response = 'Y' then
  443.   begin
  444.  
  445.     write('Name of file ?');
  446.     ReadWord(input, newhashname);
  447.     readln;
  448.     open(NewHashFile, newhashname);
  449.     rewrite(NewHashFile);
  450.  
  451.     for i := 1 to hashsize do
  452.     with hash[i] do begin
  453.       write(NewHashFile, pg:4, ' ');
  454.       j := 1;
  455.       repeat
  456.         write(NewHashFile, wd[j]);
  457.         j := j + 1;
  458.       until (wd[j] = ' ') or (j >= maxwd);
  459.       writeln(NewHashFile)
  460.     end;
  461.     close(NewHashFile)
  462.   end
  463. end;                            {procedure Conclude}
  464.  
  465.  
  466. begin                                          {procedure  SplitWords}
  467.   Initialize;                   {sets up files, hash table, constants}
  468.   GetWord(w);                       {obtain a single word from InText}
  469.   while not endinput do
  470.   begin
  471.     x := HashAddress(w);
  472.     if w = hash[x].wd then
  473.       hash[x].pg := hash[x].pg + 1
  474.     else begin                  {not in hash table; put into RefFile}
  475.       code := FindFile( w[1] );
  476.       outcount[code] := outcount[code] + 1;
  477.       with RefFile[code]^ do
  478.       begin
  479.         wd := w;
  480.         pg := pagecount
  481.       end;
  482.       Put(RefFile[code])
  483.     end;
  484.     GetWord(w);
  485.   end;
  486.   Conclude                           {writes word counts to output.}
  487. end;                                          {procedure SplitWords}
  488.  
  489.  
  490.  
  491. {start of phase 2}
  492.  
  493.  
  494. procedure ClassifyWords;
  495. {For each letter of the alphabet, the procedure reads in a list of
  496.  words from InIndex, builds them into a binary tree, supplements it
  497.  with entries from RefFile, and writes the result to files NewIndex
  498.  and NewHashFile.}
  499.  
  500. type
  501.   wordtype  = (hash, count, page, question, index); {ways to process a word}
  502.   pointref  = ^reflist;
  503.   reflist   = record                            {list of references}
  504.                 pg:   integer;
  505.                 next: pointref
  506.               end;
  507.   pointer   = ^node;
  508.   node      = record                    {vertex of the binary tree}
  509.                 wd:       word;
  510.                 left,
  511.                 right:    pointer;
  512.                 ct:       integer;
  513.                 case kind:  wordtype of
  514.                   hash, count:
  515.                     ();
  516.                   page, question, index:
  517.                     (ref:   pointref)
  518.               end;
  519. var
  520.   root:       pointer;                    {root of binary tree}
  521.   code:       filecode;          {loop through temporary files}
  522.   endlist:    Boolean;             {at end of input word list?}
  523.   i:          integer;          {general purpose loop variable}
  524.  
  525.  
  526. procedure BuildTree(var root: pointer;  code: filecode);
  527.  
  528. {Reads a sequential file in alphabetical order, and converts it into
  529.  a binary search tree. Stops reading when the first letter of word
  530.  is after lastletter[code].
  531.  const  maxheight = 20  (in main program) allows 512k entries.}
  532.  
  533. {This procedure was modified slightly to fit the needs of this application.
  534.  The parameters of GetNode now include a character ch, which has also
  535.  been introduced as a local variable.}
  536.  
  537. type
  538.   level = -1 .. maxheight;      {number of steps above leaves}
  539.  
  540. var
  541.   lastnode:  array[level] of pointer;   {contains pointer to
  542.                          last node processed on each level}
  543.   counter:   integer;           {number of nodes read in so far}
  544.   p:         pointer;           {p^ is present input node}
  545.   lev:       level;             {level of p^}
  546.   ch:        char;              {will be last letter to be processed.}
  547.  
  548.  
  549.   function Power2(c:  integer): level;
  550.   {finds the highest power of 2 which divides c}
  551.   var
  552.     lev:   level;
  553.   begin                           {function Power2}
  554.     lev := 0;
  555.     while not odd(c) do
  556.     begin
  557.       c := c div 2;
  558.       lev := lev + 1
  559.     end;
  560.     Power2 := lev
  561.   end;                            {function Power2}
  562.  
  563.  
  564.   procedure Insert(p: pointer);
  565.   {Inserts p^ as rightmost node of a partial binary search tree.}
  566.   var
  567.     lev:       level;      {level of p^}
  568.   begin                    {Procedure Insert}
  569.     lev      := Power2(counter);
  570.     p^.right := nil;
  571.     p^.left  := lastnode[lev - 1];
  572.     lastnode[lev] := p;
  573.     if lastnode[lev + 1] <> nil then
  574.       with lastnode[lev + 1]^ do
  575.       if right = nil then right := p
  576.   end;                     {Procedure Insert}
  577.  
  578.  
  579.   procedure FindRoot;
  580.   var
  581.     lev:    level;
  582.   begin                    {Procedure FindRoot}
  583.     if counter = 0 then
  584.       root := nil          {Tree is empty.}
  585.     else begin             {Non-empty tree}
  586.       lev := maxheight;    {Find the highest occupied level; it gives the root}
  587.       while lastnode[lev] = nil do lev := lev - 1;
  588.       root := lastnode[lev]
  589.     end
  590.   end;                     {Procedure FindRoot}
  591.  
  592.  
  593.   procedure ConnectSubtrees;
  594.   var
  595.     p:         pointer;
  596.     lev:       level;
  597.     s:         level;
  598.   begin                    {Procedure ConnectSubtrees}
  599.     lev := maxheight;
  600.     while (lastnode[lev] = nil) and (lev > 1) do
  601.       lev := lev - 1;      {Find the highest node:  root}
  602.     while lev > 1 do       {Nodes on levels 1 and 0 are already OK}
  603.       with lastnode[lev]^ do
  604.       if right <> nil then
  605.         lev := lev - 1     {Search down for the highest dangling node}
  606.       else begin           {Case:  right subtree is undefined.}
  607.         p := left;         {Find the highest entry in lastnode that}
  608.         s := lev - 1;                     {is not in the left subtree.}
  609.         repeat
  610.           p := p^.right;
  611.           s := s - 1
  612.         until (p = nil) or (p <> lastnode[s]);
  613.         right := lastnode[s];
  614.         lev := s           {Nodes on levels between lev and s are on the left.}
  615.       end                  {Connecting dangling subtrees}
  616.   end;                     {Procedure  ConnectSubtrees}
  617.  
  618.  
  619.   procedure GetNode( var p: pointer;  ch:  char);
  620.   {reads a word from file  InIndex  and sets node correspondingly}
  621.   {returns p = nil at eof or when next word starts later than code.}
  622.   var
  623.     wordcode:  char;                {letter indicating type of word}
  624.  
  625.   begin                                          {procedure GetNode}
  626.     while InIndex^ = '&' do         {ignore lines starting with '&'}
  627.       readln(InIndex);
  628.     while (not eof(InIndex)) and (InIndex^ = blank) do
  629.       get(InIndex);                        {Skip all leading blanks}
  630.     if endlist or eof(InIndex) then
  631.       p := nil
  632.     else if InIndex^ > ch then
  633.       p := nil
  634.    else begin
  635.       new(p);
  636.       with p^ do begin
  637.         ReadWord(InIndex, wd);
  638.         while (InIndex^ = ' ') and (not eoln(InIndex)) do
  639.           get(InIndex);
  640.         read(InIndex, wordcode);
  641.         ct := 0;
  642.         if wordcode in ['C', 'H','I','P','?'] then
  643.         case wordcode of
  644.           'C':  kind := count;
  645.  
  646.           'H':  begin
  647.                   writeln('Warning: The input word list contains ', wd);
  648.                   writeln('         which belongs in the hash table.');
  649.                   kind := hash
  650.                 end;
  651.  
  652.           'I':  begin kind := index;     ref := nil  end;
  653.           'P':  begin kind := page;      ref := nil  end;
  654.           '?':  begin
  655.                   writeln('Questionable word: ', wd, ' in word list.');
  656.                   write('New category (P, I, C, H, ?');
  657.                   repeat
  658.                     readln(wordcode);
  659.                     if wordcode > 'Z' then 
  660.                       wordcode := chr(ord(wordcode) - changecase)
  661.                   until wordcode in ['H','C','P','?','I'];
  662.                   case wordcode of
  663.                     'H':       kind := hash;
  664.                     'C':       kind := count;
  665.                     'P', ' ':  kind := page;
  666.                     '?':       kind := question;
  667.                     'I':       kind := index
  668.                   end;
  669.                   if kind in [page, question, index] then ref := nil
  670.                 end
  671.           end
  672.           else
  673.             writeln('Erroneous word code ', wordcode, ' in file InIndex.')
  674.       end;                           {with statement setting up the node}
  675.       readln(InIndex);          {Advance to the start of the next entry.}
  676.       endlist := eof(InIndex)
  677.     end
  678.   end;                                                {procedure GetNode}
  679.  
  680.  
  681. begin                           {procedure BuildTree}
  682.   for lev := -1 to maxheight do  lastnode[lev] := nil;
  683.   counter := 0;
  684.   ch := lastletter[code];
  685.   GetNode(p, ch);
  686.   while p <> nil do
  687.   begin
  688.     counter  := counter + 1;
  689.     Insert(p);
  690.     GetNode(p, ch)
  691.   end;                          {reading and processing input}
  692.   FindRoot;
  693.   ConnectSubtrees
  694. end;                            {procedure  BuildTree}
  695.  
  696.  
  697. procedure Process( r: reference);
  698. {Takes the word and page reference r, and updates the binary tree.}
  699. var
  700.   p:          pointer;                      {trace through the tree}
  701.   found:      Boolean;                    {Is the word in the tree?}
  702.  
  703.  
  704. procedure UpdateNode( p:  pointer;  r: reference);
  705. {uses reference r to update information in node p^}
  706.  
  707. var
  708.   q:     pointref;              {used to add reference to list}
  709. begin                                    {procedure UpdateNode}
  710.   with p^ do
  711.   begin
  712.     ct := ct + 1;
  713.     if  kind  in  [page, question, index] then
  714.       if ref = nil then
  715.       begin
  716.         new(ref);
  717.         ref^.pg   := r.pg;
  718.         ref^.next := nil
  719.       end
  720.       else if ref^.pg <> r.pg then
  721.       begin                     {add the new reference to list.}
  722.         new(q);
  723.         q^.pg   := r.pg;
  724.         q^.next := ref;
  725.         ref     := q
  726.       end
  727.   end                           {with statement to update tree}
  728. end;                            {procedure UpdateNode}
  729.  
  730.  
  731. procedure NewWord(var p: pointer;  r: reference);
  732. {Creates a node for the first occurrence of a new reference r. A
  733.  pointer to the new node is returned in p.}
  734.  
  735. var
  736.   response:       char;                {answer received from user}
  737. begin                                          {procedure NewWord}
  738.   new(p);
  739.   with p^ do
  740.   begin
  741.     wd    := r.wd;
  742.     left  := nil;
  743.     right := nil;
  744.     ct    := 1;
  745.  
  746.     kind  := question;
  747.     repeat                           {ask user what kind of word}
  748.       WriteWord(output, wd);
  749.       write('  is (H, C, P, ?, I)?');
  750.       readln(response);
  751.       if response > 'Z' then response := chr(ord(response) - changecase)
  752.     until response in ['H', 'C', 'P', ' ', '?', 'I'];
  753.     case response of
  754.       'H':         kind := hash;
  755.       'C':         kind := count;
  756.       'P', ' ':    kind := page;
  757.       '?':         begin
  758.                      kind := question;
  759.                      writeln('First occurence of word is on page', r.pg:5, '.')
  760.                    end;
  761.       'I':         kind := index
  762.     end;  {case statement}
  763.     if kind in [page, question, index] then
  764.     begin
  765.       new(ref);
  766.       ref^.pg   := r.pg;
  767.       ref^.next := nil;
  768.     end
  769.   end                                          {with statement}
  770. end;                                        {procedure NewWord}
  771.  
  772.  
  773. procedure InsertTree(r, p: pointer);
  774. {adds a node p^ to the tree with root r^; requires that r <> nil
  775.  and p^ not be in the tree; proceeds by recursion}
  776.  
  777. begin                           {procedure InsertTree}
  778.   if Lt(p^.wd, r^.wd) then
  779.     if r^.left = nil then r^.left := p
  780.     else InsertTree(r^.left, p)
  781.   else
  782.     if r^.right = nil then r^.right := p
  783.     else InsertTree(r^.right, p)
  784. end;                            {procedure InsertTree}
  785.  
  786.  
  787. begin                                        {procedure Process}
  788.   if root = nil then                  {The tree might be empty.}
  789.     NewWord(root, r)
  790.   else begin                            {case of non-empty tree}
  791.     p := root;                            {Begin a tree search.}
  792.     found := false;
  793.     repeat
  794.       if r.wd = p^.wd then
  795.         found := true
  796.       else if Lt(r.wd,p^.wd) then
  797.         p := p^.left
  798.       else
  799.         p := p^.right
  800.     until found or (p = nil);
  801.  
  802.     if found then UpdateNode(p, r)
  803.     else begin                  {p^ was not found: add it to the tree.}
  804.       NewWord(p, r);
  805.       InsertTree(root, p)
  806.     end
  807.   end
  808. end;                                               {procedure Process}
  809.  
  810.  
  811. procedure OutputTree( p: pointer);
  812. {traverses the tree for which p^ is the root in inorder}
  813.  
  814. procedure PutNode( p:  pointer);
  815. {Puts the information in p^ into the file NewIndex.}
  816.  
  817. var
  818.   q:        pointref;           {used to traverse list of references}
  819.   response: char;
  820. begin                                             {procedure PutNode}
  821.   with p^ do  if ct > 0 then
  822.   begin                         {Otherwise, word is not in document.}
  823.     if kind <> hash then
  824.       WriteWord(NewIndex, wd);
  825.     case kind of
  826.       hash:      begin      {new hash entries written to NewHashFile}
  827.                    WriteWord(NewHashFile, wd);
  828.                    writeln(NewHashFile)
  829.                  end;
  830.       count:     write(NewIndex, 'C');
  831.       page:      write(NewIndex, 'P');
  832.       index:     write(NewIndex, 'I');
  833.       question:
  834.         begin
  835.           repeat                      {ask user what kind of word}
  836.             WriteWord(output, wd);
  837.             write('  is questionable.  Change to (h, c, p, ?, i)?');
  838.             readln(response);
  839.             if response > 'Z' then response := chr(ord(response) - changecase)
  840.           until response in ['H', 'C', 'P',' ', '?', 'I'];
  841.           case response of
  842.             'H':       begin kind := hash;   write(NewIndex, 'H') end;
  843.             'C':       begin kind := count;  write(NewIndex, 'C') end;
  844.             'P', ' ':  begin kind := page;   write(NewIndex, 'P') end;
  845.             'I':       begin kind := index;  write(NewIndex, 'I') end;
  846.             '?':       begin
  847.                          kind := question;
  848.                          write(NewIndex, '?');
  849.                          write('The word appears on the following page(s)');
  850.                          q := ref;
  851.                          repeat
  852.                            write(q^.pg:6);
  853.                            q := q^.next
  854.                          until q = nil;
  855.                          writeln
  856.                        end                  {case of questionable word}
  857.           end                                 {case response statement}
  858.         end                            {treating new or question words}
  859.     end;                                {case kind statement}
  860.     if kind <> hash then
  861.       write(NewIndex, ct:6);
  862.     if kind in [page, question, index] then
  863.     begin
  864.       q := ref;
  865.       RowLength := 28;   {ensures that record will not exceed desired length}
  866.       repeat
  867.         if RowLength > (MaxRowLength - 4) then
  868.           begin
  869.             writeln(NewIndex);
  870.             write(NewIndex,'&  ');     {& indicates continuation of index}
  871.             RowLength := 3
  872.           end;
  873.         write( NewIndex, q^.pg:4);
  874.         q := q^.next;
  875.         RowLength := RowLength + 4
  876.       until q = nil;
  877.     end;
  878.     if kind <> hash then
  879.       writeln( NewIndex )
  880.   end                           {with statement and if statement}
  881. end;                            {procedure PutNode}
  882.  
  883.  
  884. begin                                       {procedure OutputTree}
  885.   if p <> nil then
  886.   with p^ do
  887.   begin
  888.     OutputTree(left);                  {Traverse the left subtree}
  889.     PutNode(p);
  890.     OutputTree(right);                {Traverse the right subtree}
  891.     dispose(p)
  892.   end
  893. end;                                        {procedure OutputTree}
  894.  
  895.  
  896.  
  897. begin                           {procedure ClassifyWords}
  898.  
  899.   write('Name of input word list ?');
  900.   ReadWord(input, inlistname);
  901.   readln;
  902.   open(InIndex, inlistname, readonly);
  903.   reset(InIndex);
  904.   endlist := eof(InIndex);
  905.  
  906.   write('Name of output word list ?');
  907.   ReadWord(input, newlistname);
  908.   readln;
  909.   open(NewIndex, newlistname);
  910.   rewrite(NewIndex);
  911.  
  912.   writeln('Rewriting NEWHASHFILE.DAT to contain all new hash words, if any.');
  913.   rewrite(NewHashFile);
  914.  
  915.   writeln('At the appearance of each word, indicate its disposition:');
  916.   writeln('  H -  Place this word in hash table and count its frequency.');
  917.   writeln('  C -  Count how many times this word appears.');
  918.   writeln('  P -  List pages on which this word appears.');
  919.   writeln('  ? -  Question this word: list pages on which it appears.');
  920.   writeln('  I -  Index this word: list pages on which it appears.');
  921.  
  922.   for code := 1 to nfiles do      {start main loop through temporary files.}
  923.   begin
  924.     BuildTree(root, code);  {Get the part of master wordlist starting with
  925.                code from the file InIndex, and build it into a binary tree.}
  926.     reset(RefFile[code]);
  927.     for i := 1 to outcount[code] do
  928.     begin
  929.       Process(RefFile[code]^);
  930.                      {use new words from RefFile[code] to update the tree.}
  931.       get( RefFile[code] )
  932.     end;
  933.  
  934.     OutputTree(root)
  935.                    {write the contents of the tree into file NewIndex.}
  936.   end                                    {main loop on temporary files}
  937. end;                                          {procedure ClassifyWords}
  938.  
  939. {end of all procedures}
  940.  
  941.  
  942. begin                                                    {main program}
  943.   SetTimer;
  944.   SplitWords;                                                 {Phase 1}
  945.   writeln('Time in first phase is ', ElapsedTime:7:1, '   seconds.');
  946.   writeln;
  947.  
  948.   ClassifyWords;                                              {Phase 2}
  949.   writeln('Time in second phase is', ElapsedTime:7:1, '  seconds.');
  950.  
  951.   writeln;
  952.   writeln('Processing of input document ', intextname, '  is complete.');
  953.   writeln('Total time in program was ', TotalTime:7:1, '   seconds.')
  954. end.
  955.  
  956.